package de.dnb.basics.applicationComponents;

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.font.NumericShaper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Scanner;
import java.util.StringTokenizer;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;

import javax.swing.JOptionPane;

import de.dnb.basics.Constants;
import de.dnb.basics.filtering.FilterUtils;
import de.dnb.basics.filtering.IFunction;
import de.dnb.basics.filtering.IPredicate;
import de.dnb.gnd.parser.RecordParser;
import de.dnb.gnd.parser.tag.EmptyTagDB;
import de.dnb.gnd.utils.RecordUtils;

public class StringUtils {

    // --------------------------------------------------------------------
    // Splitten nach versch. Kriterien
    // --------------------------------------------------------------------

    private static final Pattern patDollar = Pattern.compile(" *\\$ *"
        + "| *; *" + "| */ *" + "| *, *");

    private static final Pattern patEckigRechts = Pattern.compile("\\]");

    // Splittet nach ; / und $
    public static String[] splitString(final String s) {
        if (s == null)
            return null;
        if (s.length() == 0)
            return null;
        return patDollar.split(s);
    }

    // --------------------------------------------------------------------

    private static final Pattern patSlash = Pattern.compile(" / ");

    // Splittet nach /
    public static String[] splitSlash(final String s) {
        if (s == null)
            return null;
        if (s.length() == 0)
            return null;
        return patSlash.split(s);
    }

    // --------------------------------------------------------------------

    // gibt Teilstring in eckiger Klammer und Teilstring außerhalb zurück
    public static String[] verarbeiteEckigeKlammern(String line) {
        if (line == null)
            return null;
        if (line.length() == 0)
            return null;
        if (line.charAt(0) == '[') {
            line = line.substring(1);
            return patEckigRechts.split(line);
        }
        return null;
    }

    // --------------------------------------------------------------------
    // IDN-Funktionen und Patterns
    // --------------------------------------------------------------------

    /**
     * Liefer die erkannte idn oder null.
     * Extrahiert aus der eingelesenen Kopfzeile die IDN. Führende
     * Leerzeichen und -zeilen werden ignoriert.
     * 
     * @param line
     *            Kopfzeile eines neuen Datensatzes
     *            
     * @return Gültige IDN oder null
     */
    public static String extractIDN(final CharSequence line) {
        RangeCheckUtils.assertReferenceParamNotNull("line", line);
        String lineTrimmed = line.toString().trim();
        String[] lines = lineTrimmed.split("\n");
        String firstLine = lines[0];
        Matcher mIDN = RecordUtils.PAT_IDN.matcher(firstLine);
        if (mIDN.find()) {
            String idn = mIDN.group();
            char first = idn.charAt(0);
            if (first == Constants.MARC_SUB_SEP) {
                idn = idn.substring(2);
            } else if (!Character.isDigit(first)) {
                idn = idn.substring(1);
            }
            return idn;
        }
        return null;
    }

    /**
     * 
     * Zerlegt die Kopfzeile beim pica3-Download in die Inhalte der Felder
     * 001A, 001B, 001D.
     * 
     * @param line nicht null
     * @return Tripel aus Eingabe, Änderung und Status oder null
     */
    public static Quadruplett<String, String, String, String> getControlFields(
        final String line) {
        RangeCheckUtils.assertReferenceParamNotNull("line", line);
        //@formatter:off
		final Pattern controlPat =
			Pattern.compile(
				"Eingabe: (\\S+) Änderung: (\\S+) (\\S*) Status: (\\S+)\\s*");
		//@formatter:on
        Matcher m = controlPat.matcher(line);
        if (m.matches()) {
            String eingabe = m.group(1);
            String aenderung = m.group(2);
            String time = m.group(3);
            String status = m.group(4);
            //@formatter:off
            return new Quadruplett<String, String, String, String>(
                    eingabe, aenderung, time, status);
            //@formatter:on
        }
        return null;

    }

    /**
     * Simuliert eine Kopfzeile, wie sie beim Download erzeugt wird.
     * @return
     */
    public static String kopfzeile() {
        return "SET: S1 [14] TTL: 1           "
            + "PPN: 972620788                            " + "SEITE1 .";
    }

    /*
     * Simuliert eine Kopfzeile mit idn, wie sie beim Download erzeugt wird
     */
    public static String kopfzeile(final int nr, final String idn) {
        return "SET: S1 [14] TTL: " + nr + "           " + "PPN: " + idn
            + "                            " + "SEITE1 .";
    }

    // ----------------------------------------------------------------

    // ----------------------------------------------------------------

    private static final Pattern patSonderzeichen = Pattern // ?
        .compile(".*[^@,\\s\\w<>äöüÄÖÜß§\\$%&-].*");

    public static boolean containsSonderzeichen(final String s) {
        return patSonderzeichen.matcher(s).matches();
    }

    // -------------------------------------------------------------------

    private static final Pattern patgezaehlteUnterabteilung = Pattern
        .compile("(.*-\\d*)");

    public static boolean containsGezUnterabt(final String s) {
        return patgezaehlteUnterabteilung.matcher(s).matches();
    }

    /*
     * Gibt einen Text mit Zeilenumbrüchen in vorgegebener Zeilenlänge zurück.
     * Text darf noch keine Zeilenumbrüche enthalten.
     */
    public static String maximaleZeilenlaenge(final String s, final int len) {
        StringTokenizer st = new StringTokenizer(s, " ", true);
        String word, ausgabe = "";
        int currentLineLen = 0;

        while (st.hasMoreTokens()) {
            word = st.nextToken();
            int wordLen = word.length();

            if (currentLineLen + wordLen <= len) {
                ausgabe += word;
                currentLineLen += wordLen;
            } else { // Zeilenumbruch erforderlich
                ausgabe += Constants.LINE_SEPARATOR;
                boolean firstIsSpace = (word.charAt(0) == ' ');
                ausgabe += (firstIsSpace ? "" : word);
                currentLineLen = firstIsSpace ? 0 : wordLen;
            }
        }
        return ausgabe;
    }

    /*
     * Rückt Folgezeilen eines String um anz Tabs nach rechts.
     */
    public static String folgezeilenEinruecken(final String s, final int anz) {
        StringBuffer ein = new StringBuffer("");
        for (int i = 0; i < anz; i++) {
            ein.append("\t");
        }
        String einrueckung = Constants.LINE_SEPARATOR + ein.toString();
        // Ersetze alle "\n" durch "\n\t..."

        // s = s.replaceAll("\n", a);
        return s.replaceAll(Constants.LINE_SEPARATOR, einrueckung);
    }

    public static String ersterBuchstabeGross(final String string) {
        if (string == null || string.length() == 0)
            return string;

        return new StringBuffer(string.length())
            .append(Character.toTitleCase(string.charAt(0)))
            .append(string.substring(1)).toString();
    }

    public static String ersterBuchstabeKlein(final String string) {
        if (string == null || string.length() == 0)
            return string;

        return new StringBuffer(string.length())
            .append(Character.toLowerCase(string.charAt(0)))
            .append(string.substring(1)).toString();
    }

    static final Pattern patYear = Pattern.compile("\\d\\d\\d\\d");

    public static boolean containsYear(final String s) {
        Matcher m = patYear.matcher(s);
        return m.find();
    }

    /**
     * Entfernt aus input alle Zeilen, die mit prefix anfangen.
     * 
     * @param input		nicht null
     * @param prefix	nicht null
     * @return			input ohne Zeilen mit prefix und ohne Leerzeilen 
     *					(Zeilen nur mit Whitespace)
     */
    public static
        String
        removeLinesFromString(final String input, String prefix) {
        RangeCheckUtils.assertReferenceParamNotNull("input", input);
        RangeCheckUtils.assertReferenceParamNotNull("prefix", prefix);
        prefix = Pattern.quote(prefix);
        String s = input;
        String regex = "^" + prefix + ".*$";
        Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
        Matcher matcher = pattern.matcher(input);
        s = matcher.replaceAll("");

        // Leerzeilen entfernen:
        s =
            s.replaceAll("(\\s*" + "\n" + "" + "\\s*)+",
                Constants.LINE_SEPARATOR);
        return s.trim();

    }

    /**
     * Liefert den Inhalt aller Felder mit tag und aller Unterfelder
     * mit ind. 
     * Dafür werden String-Daten im Pica+-Format mit 'ƒ' als Unterfeldtrenner
     * benötigt.
     * 
     * @param tag 		nicht null, nicht leer.
     * @param ind		beliebig.
     * @param rawData	nicht null, nicht leer.
     * 
     * @return			nicht null.
     */
    public static List<String> getPicaPlusContents(
        final String tag,
        final char ind,
        final String rawData) {
        RangeCheckUtils.assertStringParamNotNullOrWhitespace("tag", tag);
        RangeCheckUtils
            .assertStringParamNotNullOrWhitespace("rawData", rawData);
        List<String> strings = new LinkedList<String>();
        String linePatStr = "^" + tag + ".*$";
        Pattern linePat = Pattern.compile(linePatStr, Pattern.MULTILINE);
        Matcher lineMat = linePat.matcher(rawData);
        while (lineMat.find()) {
            String line = lineMat.group();
            String subfieldStr = "ƒ" + ind + "([^ƒ]*)";
            Pattern subfieldPat =
                Pattern.compile(subfieldStr, Pattern.MULTILINE);
            Matcher subMatcher = subfieldPat.matcher(line);
            while (subMatcher.find()) {
                strings.add(subMatcher.group(1));
            }
        }
        return strings;

    }

    /**
     * Liefert den des ersten Feldes mit tag und des ersten Unterfeldes
     * mit ind. 
     * Dafür werden String-Daten im Pica+-Format mit 'ƒ' als Unterfeldtrenner
     * benötigt.
     * 
     * @param tag 		nicht null, nicht leer.
     * @param ind		beliebig.
     * @param rawData	nicht null, nicht leer.
     * 
     * @return			Inhalt des Unterfeldes oder null.
     */
    public static String getPicaPlusContent(
        final String tag,
        final char ind,
        final String rawData) {
        RangeCheckUtils.assertStringParamNotNullOrWhitespace("tag", tag);
        RangeCheckUtils
            .assertStringParamNotNullOrWhitespace("rawData", rawData);
        List<String> strings = getPicaPlusContents(tag, ind, rawData);
        if (strings.isEmpty())
            return null;
        else
            return strings.get(0);
    }

    /**
     * Sichere Methode, um ein Zeichen an der Stelle index im String s zu 
     * finden.
     * 
     * @param s		beliebig.
     * @param index	beliebig.
     * 
     * @return		Zeichen an der Stelle index, 0 in allen anderen Fällen.
     */
    public static char charAt(final String s, final int index) {
        if (s == null)
            return 0;
        if (index < 0)
            return 0;
        if (index >= s.length())
            return 0;
        return s.charAt(index);
    }

    /**
     * 
     * Null-sichere Methode, auf ein Element eines Arrays zuzugreifen.
     * 
     * @param collection	beliebig
     * @param index			beliebig
     * @param <V>			Typ
     * @return				Wert an der Stelle i, sonst null
     */
    static <V> V get(final V[] collection, final int index) {
        if (collection == null)
            return null;
        if (index < 0)
            return null;
        if (index >= collection.length)
            return null;
        return collection[index];

    }

    public static boolean isNullOrEmpty(String s) {
        return s == null || s.isEmpty();
    }

    /**
     * Enthält eines der Elemente aus iterable das Präfix?
     * 
     * @param iterable	nicht null.
     * @param prefix	nicht null.
     * @return			true, wenn prefix vorkommt.
     */
    public static boolean containsPrefix(
        Iterable<String> iterable,
        String prefix) {
        RangeCheckUtils.assertReferenceParamNotNull("iterable", iterable);
        RangeCheckUtils.assertStringParamNotNullOrEmpty("prefix", prefix);
        IPredicate<String> predicate = new PrefixPredicate(prefix);
        return FilterUtils.contains(iterable, predicate);
    }

    /**
     * Liest einen String aus der Zwischenablage.
     * 
     * @return String oder null, wenn kein String in Zwischenablage.
     */
    public static String readClipboard() {
        Clipboard systemClipboard;
        systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        Transferable transferData = systemClipboard.getContents(null);
        if (transferData != null
            && transferData.isDataFlavorSupported(DataFlavor.stringFlavor)) {
            // Inhalt auslesen
            try {
                return (String) transferData
                    .getTransferData(DataFlavor.stringFlavor);
            } catch (UnsupportedFlavorException e) {
                return null;
            } catch (IOException e) {
                return null;
            }
        }
        return null;
    }

    /**
     * Liest einen String aus der Zwischenablage und zerlegt ihn in
     * einzelne Zeilen.
     * 
     * @return	Liste der Zeilen, nicht null
     */
    public static Collection<String> readLinesFromClip() {
        String s = StringUtils.readClipboard();
        if (s == null)
            return Collections.emptyList();
        String[] list = s.split("\n");
        return Arrays.asList(list);
    }

    /**
     * Macht aus einem String eine Tabelle. \n leitet eine
     * neue Zeile, \t eine neue Spalte ein.
     * 
     * @param s		auch null
     * @return		eine Tabelle als Array mit 2 Parametern, 
     */
    public static String[][] makeTable(final String s) {
        if (s == null)
            return new String[0][0];
        String[] lines = s.split("\n");
        String[][] table = new String[lines.length][];
        for (int i = 0; i < lines.length; i++) {
            table[i] = lines[i].split("\t");
        }
        return table;
    }

    /**
     * Null-sichere Methode, ein 2-dimensionales Array auszulesen.
     * 
     * @param table		beliebig
     * @param row		beliebig, für echte Tabellen bei 0 beginnend
     * @param column	beliebig, für echte Tabellen bei 0 beginnend
     * @param <V>		Typ
     * @return			Wert oder null
     */
    public static <V> V getCellAt(
        final V[][] table,
        final int row,
        final int column) {
        if (table == null)
            return null;
        if (row < 0 || column < 0 || row >= table.length)
            return null;
        V[] line = table[row];
        if (column >= line.length)
            return null;
        return line[column];
    }

    /**
     * Null-sichere Methode, eine Excel-Tabelle, gegeben als 
     * 2-dimensionales Array auszulesen.
     * 
     *@param table		beliebig
     * @param row		beliebig, für echte Tabellen bei 1 beginnend
     * @param column	beliebig, für echte Tabellen bei 'A' beginnend
     * @param <V>		Typ
     * @return			Wert oder null
     */
    public static <V> V getExcelCellAt(
        final V[][] table,
        final int row,
        final char column) {
        return getCellAt(table, row - 1, column - 'A');
    }

    public static String readConsole() {
        Scanner scanner = new Scanner(System.in);
        String s = scanner.nextLine();
        FileUtils.safeClose(scanner);
        return s;
    }

    /**
     * Schreibt einen String in die Zwischenablage.
     * 
     * @param s	nicht null.
     */
    public static void writeToClipboard(String s) {
        RangeCheckUtils.assertReferenceParamNotNull("s", s);
        Toolkit.getDefaultToolkit().getSystemClipboard()
            .setContents(new StringSelection(s), null);
    }

    /**
     * @param repeat  number of times to repeat delim
     * @param padChar  character to repeat
     * @return String with repeated character
     */
    public static String padding(final int repeat, final char padChar) {
        if (repeat < 0) {
            throw new IndexOutOfBoundsException(
                "Cannot pad a negative amount: " + repeat);
        }
        final char[] buf = new char[repeat];
        Arrays.fill(buf, padChar);
        return new String(buf);
    }

    /**
     * @param repeat  number of times to repeat string
     * @param string  string to repeat
     * @return repeated string
     */
    public static String repeat(final int repeat, final String string) {
        if (repeat < 0) {
            throw new IndexOutOfBoundsException(
                "Cannot pad a negative amount: " + repeat);
        }
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < repeat; i++) {
            buffer.append(string);
        }
        return buffer.toString();
    }

    /**
     * Füllt String links mit padChar auf bis zur gewünschten Länge minLength.
     * @param s				nicht null
     * @param minLength		beliebig
     * @param padChar		beliebig
     * @return	s, wenn s.length()  >minLength; sonst s aufgefüllt.
     */
    public static String leftPadding(
        String s,
        final int minLength,
        final char padChar) {
        RangeCheckUtils.assertReferenceParamNotNull("s", s);
        int sLength = s.length();
        if (sLength >= minLength)
            return s;
        else
            return padding(minLength - sLength, padChar) + s;
    }

    /**
     * Füllt String rechts mit padChar auf bis zur gewünschten Länge minLength.
     * @param s             nicht null
     * @param minLength     beliebig
     * @param padChar       beliebig
     * @return  s, wenn s.length()  >minLength; sonst s aufgefüllt.
     */
    public static String rightPadding(
        String s,
        final int minLength,
        final char padChar) {
        RangeCheckUtils.assertReferenceParamNotNull("s", s);
        int sLength = s.length();
        if (sLength >= minLength)
            return s;
        else
            return s + padding(minLength - sLength, padChar);
    }

    /**
     * Sortiert eine Liste von Strings nach den ersten 3 Zeichen (Tags).
     * @param strings	nicht null.
     */
    public static void tagSort(List<String> strings) {
        RangeCheckUtils.assertReferenceParamNotNull("strings", strings);
        IPredicate<String> predicate = new IPredicate<String>() {
            @Override
            public boolean accept(final String string) {
                return string != null && string.length() > 3;
            }
        };
        FilterUtils.filter(strings, predicate);
        Comparator<String> comparator = new Comparator<String>() {

            @Override
            public int compare(final String o1, final String o2) {
                String prefix1 = o1.substring(0, 2);
                String prefix2 = o2.substring(0, 2);
                return prefix1.compareTo(prefix2);
            }
        };
        Collections.sort(strings, comparator);
    }

    /**
     * Macht eine Objekt-Liste zu einem einzigen String mit
     * Zeilenumbrüchen.
     * 
     * @param objects	nicht null
     * @return			String mit Umbrüchen, nicht null.
     */
    public static String concatenate(final Iterable<? extends Object> objects) {
        RangeCheckUtils.assertReferenceParamNotNull("strings", objects);
        return concatenate(Constants.LINE_SEPARATOR, objects);
    }

    /**
     * Macht eine Objekt-Liste zu einem einzigen String mit
     * Zeilenumbrüchen.
     * 
     * @param objects	nicht null
     * @return			String mit Umbrüchen, nicht null.
     */
    public static String concatenate(final String separator, Object... objects) {
        RangeCheckUtils.assertReferenceParamNotNull("separator", separator);
        RangeCheckUtils.assertArrayParamNotNullOrEmpty("strings", objects);
        return concatenate(separator, Arrays.asList(objects));
    }

    /**
     * Macht eine Objekt-Liste zu einem einzigen String mit
     * Trennung durch separator.
     * @param separator beliebig
     * @param objects	nicht null
     * 
     * @return			String mit separator, nicht null.
     */
    public static String concatenate(
        final String separator,
        final Iterable<? extends Object> objects) {
        return concatenate(separator, objects, null);
        //		RangeCheckUtils.assertReferenceParamNotNull("separator", separator);
        //		RangeCheckUtils.assertReferenceParamNotNull("strings", objects);
        //		String s = "";
        //		for (Iterator<? extends Object> iterator = objects.iterator(); iterator
        //			.hasNext();) {
        //			Object obj = iterator.next();
        //			s += obj;
        //			if (iterator.hasNext())
        //				s += separator;
        //		}
        //		return s;
    }

    static final IFunction<Object, String> OBJ_TO_STRING =
        new IFunction<Object, String>() {
            @Override
            public String apply(final Object obj) {
                return obj.toString();
            }
        };

    /**
     * Macht eine Objekt-Liste zu einem einzigen String mit
     * Trennung durch separator.
     * 
     * @param separator		nicht null
     * @param objects		nicht null
     * @param t2String		Umwandlungsfunktion objects -> String; wenn null,
     * 						wird object.toString() genommen.
     * @param <T>			Typ der Objekte
     * @return				Liste, die durch separator getrennt ist
     */
    public static <T> String concatenate(
        final String separator,
        final Iterable<T> objects,
        final IFunction<? super T, String> t2String) {
        RangeCheckUtils.assertReferenceParamNotNull("separator", separator);
        RangeCheckUtils.assertReferenceParamNotNull("strings", objects);
        IFunction<? super T, String> function;
        if (t2String == null)
            function = OBJ_TO_STRING;
        else
            function = t2String;
        String s = "";
        for (Iterator<T> iterator = objects.iterator(); iterator.hasNext();) {
            T obj = iterator.next();
            s += function.apply(obj);
            if (iterator.hasNext())
                s += separator;
        }
        return s;
    }

    /**
     * Macht eine Objekt-Liste zu einem einzigen String mit
     * Zeilenumbrüchen.
     * 
     * @param objects	nicht null
     * @return			String mit Umbrüchen, nicht null.
     */
    public static <T> String concatenate(
        final String separator,
        final IFunction<? super T, String> t2String,
        T... objects) {
        RangeCheckUtils.assertReferenceParamNotNull("separator", separator);
        RangeCheckUtils.assertArrayParamNotNullOrEmpty("strings", objects);
        return concatenate(separator, Arrays.asList(objects), t2String);
    }

    /**
     * Ersetzt nicht druckbare Zeichen durch eine Darstellung <<.>>.
     * @param original	nicht null
     * @return			nicht null
     */
    public static String replaceImprintables(final String original) {
        String s = original;
        s = s.replace(Constants.GS, "<<GS>>");
        s = s.replace(Constants.RS, "<<RS>>");
        s = s.replace(Constants.US, "<<US>>");
        return s;
    }

    /**
     * Sichere equals-Methode.
     * @param obj1	beliebig.
     * @param obj2	beliebig.
     * @return	gleich oder ungleich (null == null)
     */
    public static boolean equals(final Object obj1, final Object obj2) {
        if (obj1 == null) {
            return obj2 == null;
        }
        return obj1.equals(obj2);
    }

    /**
     * Entfernt die Zeichen 152 (Start Of String) und 156 (String Terminator).
     * 
     * @param txt	nicht null
     * @return		Eingabestring ohne nichtdruckbare Zeichen
     */
    public static String cleanMarc(final String txt) {

        char c156 = 156;
        String s156 = Character.toString(c156);

        char c152 = 152;
        String s152 = Character.toString(c152);

        String txt1 = txt.replace(s156, "");
        txt1 = txt1.replace(s152, "");
        txt1 = txt1.replaceAll("[\\p{Cntrl}]", "");
        return txt1;
    }

    /**
     * @param args
     * @throws IOException 
     */
    public static void main(String[] args) throws IOException {
        System.out.println(rightPadding("78", 3, '0'));
    }

    /**
     * Gibt alle Zeichen eines Strings, deren Integerwert und deren 
     * Hexcode aus.
     * 
     * @param s nicht null
     */
    public static void debugString(final String s) {
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            System.out.println(c + "/" + (int) c + "/0x"
                + Integer.toHexString(c));
        }
    }

    /**
     * Macht aus einer Liste eine Excel-Zelle mit Umbrüchen.
     * 
     * @param collection	beliebig
     * @return				Excel-Zelle; "null", wenn collection null oder leer
     */
    public static String makeExcelCellFromCollection(
        final Collection<?> collection) {
        if (collection == null)
            return "\"null\"";
        if (collection.isEmpty())
            return "\"null\"";
        String s =
            "\"" + StringUtils.concatenate("" + (char) 10, collection) + "\"";
        // sicherheitshalber:
        s = s.replace("\t", "");
        return s;
    }

    /**
     * Macht aus einem Object eine Excel-Zelle. Wenn eine das Objekt eine 
     * Collection ist, dann mit Umbrüchen.
     * 
     * @param object        beliebig
     * @return              Excel-Zelle; "null", wenn null.
     */
    public static String makeExcelCell(Object object) {
        if (object == null)
            return "\"null\"";
        if (object instanceof Collection<?>)
            return makeExcelCellFromCollection((Collection<?>) object);
        String s = "\"" + object + "\"";
        // sicherheitshalber:
        s = s.replace("\t", "");
        return s;
    }

    public static String makeExcelLine(Object... objects) {
        Collection<Object> collection = Arrays.asList(objects);
        IFunction<Object, String> makeExcelCellIF =
            new IFunction<Object, String>() {
                @Override
                public String apply(Object x) {
                    return makeExcelCell(x);
                }
            };
        return concatenate("\t", collection, makeExcelCellIF);
    }

    /**
     * 
     * @param s
     * @param fractionSize
     * @return
     */
    public static Collection<String> splitStringInEqualFractions(
        String s,
        int fractionSize) {
        Objects.requireNonNull(s);
        if (fractionSize < 0)
            throw new IllegalArgumentException("fractionSize < 0");
        Collection<String> strings = new LinkedList<>();
        StringReader reader = new StringReader(s);
        char[] buf = new char[fractionSize];
        int count;
        try {
            while ((count = reader.read(buf)) != -1) {
                String fraction = new String(buf, 0, count);
                strings.add(fraction);
            }
        } catch (IOException e) {
            // nix
        }
        return strings;
    }

}
